今天是 function 時空旅行第二天,昨天我們學會了如何收拾背包(?),也學會不要讓背包裡的東西變質(?)。。。總之就是學會了關於 function 參數的另一種使用方式,提高可維護性。
今天要來談談關於 function 的拆解與命名:
跟昨天一樣,先來個範例看看吧:
// 送出編輯個人資訊的表單
/*
* name : 姓名
* age : 年齡
* gender: 性別
*/
const requiredFields = [
'name',
'age',
'gender'
];
const submitForm = (value) => {
const formFields = Object.keys(value);
const valid = requiredFields.every(key => {
return formFields.includes(key) && typeof value[key] !== 'undefined'
});
if(!valid) {
console.log('尚有必填欄位未填');
return;
}
const nameSplitList = value.name.split(' ');
const submitValue = {
firstName: nameSplitList[0],
lastName: nameSplitList[1],
age: Number(value.age),
gender: value.gender
};
fetch('my-backend-API', {
method: 'POST',
body: JSON.stringify(submitValue)
});
};
const formValue = {
name: 'yc chiu',
age: '20',
gender: 'male'
};
submitForm(formValue);
這是模擬編輯個人資訊的表單,可以填上姓名、年齡、性別三個欄位,按下送出表單的時候,會做到以下三件事:
只有三個欄位,其實算是一個相對簡單的範例,但可以看到要送出表單時,submitForm
多達 22 行(而且 fetch
還被我偷懶簡化過)。
如果同樣的目的,我們將 submitForm
拆解,分別用以下的 function 來處理:
validateFormData
prepareSubmitData
postPersonData
// 送出編輯個人資訊的表單
/*
* name : 姓名
* age : 年齡
* gender: 性別
*/
const requiredFields = [
'name',
'age',
'gender'
];
const validateFormData = (formData) => {
const formFields = Object.keys(formData);
return requiredFields.every(key => {
return formFields.includes(key) && typeof formData[key] !== 'undefined'
});
};
const prepareSubmitData = (formData) => {
const nameSplitList = formData.name.split(' ');
return {
firstName: nameSplitList[0],
lastName: nameSplitList[1],
age: Number(formData.age),
gender: formData.gender
};
};
const postPersonData = (submitData) => {
fetch('my-backend-API', {
method: 'POST',
body: JSON.stringify(submitData)
});
};
const submitForm = (value) => {
const valid = validateFormData(value);
if(!valid) {
console.log('尚有必填欄位未填');
return ;
}
const submitData = prepareSubmitData(value);
postPersonData(submitData);
};
const formValue = {
name: 'yc chiu',
age: '20',
gender: 'male'
};
submitForm(formValue);
沒錯,改完之後程式碼更長了(傻眼),但我們換到什麼呢?
雖然程式碼更多了,但對於剛接手這份 code 的人來說,其實讀懂的速度更快了。
重點在於我們將 submitForm
這個 function 裡面,原本混雜沒有界線的邏輯,使用幾個小 function 切割開來,強迫這些邏輯拆散,就不再是一大坨程式要一行一行讀。
不過要強調的是,增加可讀性不是一定要拆 function 才做得到,比較懶人一點做法,可以加上註解:
const submitForm = (value) => {
// 檢核必填欄位
const formFields = Object.keys(value);
const valid = requiredFields.every(key => {
return formFields.includes(key) && typeof value[key] !== 'undefined'
});
if(!valid) {
console.log('尚有必填欄位未填');
return;
}
// 將欄位值轉換為後端需要的欄位與格式
const nameSplitList = value.name.split(' ');
const submitValue = {
firstName: nameSplitList[0],
lastName: nameSplitList[1],
age: Number(value.age),
gender: value.gender
};
// 發送 API request 到後端
fetch('my-backend-API', {
method: 'POST',
body: JSON.stringify(submitValue)
});
};
這是用註解也辦不到的事,是 function 的一大賣點,如果同樣或相似的邏輯,出現兩次就該考慮是否寫成 function 了,出現三次就要寫檢討報告了(?)
因為 function 的可重用性,可以大幅減少不必要的重複 code,feature 修改時只要修改一個地方,不會漏掉;要做測試的時候,也能保證結果相同,同時也提升了可維護性。
重點來了,什麼時候會需要放到 function 重用?
其實。。。單純就是。。。出現太多次的時候((拖走
比方說常用來發送 API request 的 axios
或 fetch
,就非常適合包進 function 裡面重用(部分大寫變數,不是本次重點,可自行體會XD):
const callApi = async(endpoint, method = 'get', body) => {
const requestUrl = `${API_URL}/${endpoint}`;
const options = {
headers: {
'content-type': 'application/json',
Authorization: TOKEN,
},
method
};
if (body) {
options.body = JSON.stringify(body);
}
try {
const response = await fetch(requestUrl, options);
if (response.ok) {
const json = await response.json();
return json;
}
return Promise.reject(response);
} catch (error) {
throw new Error(error);
}
};
呼叫時都只要一行
// GET
const productList = await callApi(`/product`);
// POST
const productResult = await callApi(`/product`, 'post', data);
當然,光是拆散還不夠,如果把這些小 function 命名成 apple
、banana
之類的名字,肯定也是看不懂的(應該說更加不懂),因此好的命名絕對是非常加分的!
而 function 的命名,某種程度上算是一種團隊風格,只要團隊中成員都能夠好讀、讀懂。唯一的衡量標準應該就是 predictable,容易預測、容易猜到這個 function 要做什麼,就是好命名。
而我自己遵守的主要是以下幾點:
這點算是非常好理解也容易上手,駝峰式就像是駱駝的背一樣,凹下去凸起來凹下去凸起來,小寫大寫小寫大寫,所以比起全小寫還容易閱讀。
其實照這樣說,手握拳的時候,手指根部的四個關節也是凹下去凸起來啊,怎麼不叫指關節式命名(?),是因為多一個字嗎(?)
若是遇到縮寫,則可以考慮使用底線(_),雖然沒有很建議,但也是個辦法。
所以大概是這樣:
applePie
bananaFish(?)
toDoList
HTML_Parser
function 本身就是用來「執行」一些任務的,所以必然是動詞開頭,而後面接名詞則構成一個基本的語句(主詞大概是 user 吧!)。如果 V. + N. 的組合還不夠清楚,還可以加上一些修飾詞:
validateFormData
getProductList
findUserById
確保每個動詞都意義一致,尤其是一些翻成中文相似的動詞:
patch 用於部分更新
put 用於替換
fetch 用於發送 request
get 取得的萬用字(?)
function 經過拆解之後,重新命名給予邏輯意義,雖然功能都一樣,但是當程式規模愈大,就愈能看出這樣做的好處,下次當你有以下的感覺,不妨好好考慮「拆解+命名」吧!
離開了熟悉的家鄉
改名換姓
在銀河的另一端相遇
認識一些常見的動詞,對於命名 function 還蠻有幫助的!很不錯的分享 :)
太棒了~ 不過命名在這篇算是一小部分而已,我有點想要拉出去特別寫一篇跟大家討論,覺得真的很重要!
名字取得好,看 code 沒煩惱XD